//-----------------------------------------------------------------------------
// File: DSUtil.cpp
//
// Desc: DirectSound framework classes for reading and writing wav files and
//       playing them in DirectSound buffers. Feel free to use this class 
//       as a starting point for adding extra functionality.
//
// Copyright (c) Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include <windows.h>
#include <mmsystem.h>
#include <dxerr.h>
#include <dsound.h>
#include "DSUtil.h"
#include "DXUtil.h"




//-----------------------------------------------------------------------------
// Name: CSoundManager::CSoundManager()
// Desc: Constructs the class
//-----------------------------------------------------------------------------
CSoundManager::CSoundManager()
{
	m_pDS = NULL;
}




//-----------------------------------------------------------------------------
// Name: CSoundManager::~CSoundManager()
// Desc: Destroys the class
//-----------------------------------------------------------------------------
CSoundManager::~CSoundManager()
{
	SAFE_RELEASE(m_pDS);
}




//-----------------------------------------------------------------------------
// Name: CSoundManager::Initialize()
// Desc: Initializes the IDirectSound object and also sets the primary buffer
//       format.  This function must be called before any others.
//-----------------------------------------------------------------------------
HRESULT CSoundManager::Initialize(HWND  hWnd,
	DWORD dwCoopLevel)
{
	HRESULT             hr;

	SAFE_RELEASE(m_pDS);

	// Create IDirectSound using the primary sound device
	if (FAILED(hr = DirectSoundCreate8(NULL, &m_pDS, NULL)))
		return DXTRACE_ERR(TEXT("DirectSoundCreate8"), hr);

	// Set DirectSound coop level 
	if (FAILED(hr = m_pDS->SetCooperativeLevel(hWnd, dwCoopLevel)))
		return DXTRACE_ERR(TEXT("SetCooperativeLevel"), hr);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CSoundManager::SetPrimaryBufferFormat()
// Desc: Set primary buffer to a specified format 
//       !WARNING! - Setting the primary buffer format and then using this
//                   same dsound object for DirectMusic messes up DirectMusic! 
//       For example, to set the primary buffer format to 22kHz stereo, 16-bit
//       then:   dwPrimaryChannels = 2
//               dwPrimaryFreq     = 22050, 
//               dwPrimaryBitRate  = 16
//-----------------------------------------------------------------------------
HRESULT CSoundManager::SetPrimaryBufferFormat(DWORD dwPrimaryChannels,
	DWORD dwPrimaryFreq,
	DWORD dwPrimaryBitRate)
{
	HRESULT             hr;
	LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;

	if (m_pDS == NULL)
		return CO_E_NOTINITIALIZED;

	// Get the primary buffer 
	DSBUFFERDESC dsbd;
	ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
	dsbd.dwBufferBytes = 0;
	dsbd.lpwfxFormat = NULL;

	if (FAILED(hr = m_pDS->CreateSoundBuffer(&dsbd, &pDSBPrimary, NULL)))
		return DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);

	WAVEFORMATEX wfx;
	ZeroMemory(&wfx, sizeof(WAVEFORMATEX));
	wfx.wFormatTag = (WORD)WAVE_FORMAT_PCM;
	wfx.nChannels = (WORD)dwPrimaryChannels;
	wfx.nSamplesPerSec = (DWORD)dwPrimaryFreq;
	wfx.wBitsPerSample = (WORD)dwPrimaryBitRate;
	wfx.nBlockAlign = (WORD)(wfx.wBitsPerSample / 8 * wfx.nChannels);
	wfx.nAvgBytesPerSec = (DWORD)(wfx.nSamplesPerSec * wfx.nBlockAlign);

	if (FAILED(hr = pDSBPrimary->SetFormat(&wfx)))
		return DXTRACE_ERR(TEXT("SetFormat"), hr);

	SAFE_RELEASE(pDSBPrimary);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CSoundManager::Get3DListenerInterface()
// Desc: Returns the 3D listener interface associated with primary buffer.
//-----------------------------------------------------------------------------
HRESULT CSoundManager::Get3DListenerInterface(LPDIRECTSOUND3DLISTENER* ppDSListener)
{
	HRESULT             hr;
	DSBUFFERDESC        dsbdesc;
	LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;

	if (ppDSListener == NULL)
		return E_INVALIDARG;
	if (m_pDS == NULL)
		return CO_E_NOTINITIALIZED;

	*ppDSListener = NULL;

	// Obtain primary buffer, asking it for 3D control
	ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
	dsbdesc.dwSize = sizeof(DSBUFFERDESC);
	dsbdesc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;
	if (FAILED(hr = m_pDS->CreateSoundBuffer(&dsbdesc, &pDSBPrimary, NULL)))
		return DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);

	if (FAILED(hr = pDSBPrimary->QueryInterface(IID_IDirectSound3DListener,
		(VOID**)ppDSListener)))
	{
		SAFE_RELEASE(pDSBPrimary);
		return DXTRACE_ERR(TEXT("QueryInterface"), hr);
	}

	// Release the primary buffer, since it is not need anymore
	SAFE_RELEASE(pDSBPrimary);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CSoundManager::Create()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT CSoundManager::Create(CSound** ppSound,
	LPTSTR strWaveFileName,
	DWORD dwCreationFlags,
	GUID guid3DAlgorithm,
	DWORD dwNumBuffers)
{
	HRESULT hr;
	HRESULT hrRet = S_OK;
	DWORD   i;
	LPDIRECTSOUNDBUFFER* apDSBuffer = NULL;
	DWORD                dwDSBufferSize = NULL;
	CWaveFile*           pWaveFile = NULL;
	if (m_pDS == NULL)
		return CO_E_NOTINITIALIZED;
	if (strWaveFileName == NULL || ppSound == NULL || dwNumBuffers < 1)
		return E_INVALIDARG;

	apDSBuffer = new LPDIRECTSOUNDBUFFER[dwNumBuffers];
	if (apDSBuffer == NULL)
	{
		hr = E_OUTOFMEMORY;
		goto LFail;
	}

	pWaveFile = new CWaveFile();
	if (pWaveFile == NULL)
	{
		hr = E_OUTOFMEMORY;
		goto LFail;
	}

	pWaveFile->Open(strWaveFileName, NULL, WAVEFILE_READ);

	if (pWaveFile->GetSize() == 0)
	{
		// Wave is blank, so don't create it.
		hr = E_FAIL;
		goto LFail;
	}

	// Make the DirectSound buffer the same size as the wav file
	dwDSBufferSize = pWaveFile->GetSize();

	// Create the direct sound buffer, and only request the flags needed
	// since each requires some overhead and limits if the buffer can 
	// be hardware accelerated
	DSBUFFERDESC dsbd;
	ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = dwCreationFlags;
	dsbd.dwBufferBytes = dwDSBufferSize;
	dsbd.guid3DAlgorithm = guid3DAlgorithm;
	dsbd.lpwfxFormat = pWaveFile->m_pwfx;

	// DirectSound is only guarenteed to play PCM data.  Other
	// formats may or may not work depending the sound card driver.
	hr = m_pDS->CreateSoundBuffer(&dsbd, &apDSBuffer[0], NULL);

	// Be sure to return this error code if it occurs so the
	// callers knows this happened.
	if (hr == DS_NO_VIRTUALIZATION)
		hrRet = DS_NO_VIRTUALIZATION;

	if (FAILED(hr))
	{
		// DSERR_BUFFERTOOSMALL will be returned if the buffer is
		// less than DSBSIZE_FX_MIN and the buffer is created
		// with DSBCAPS_CTRLFX.

		// It might also fail if hardware buffer mixing was requested
		// on a device that doesn't support it.
		DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);

		goto LFail;
	}

	// Default to use DuplicateSoundBuffer() when created extra buffers since always 
	// create a buffer that uses the same memory however DuplicateSoundBuffer() will fail if 
	// DSBCAPS_CTRLFX is used, so use CreateSoundBuffer() instead in this case.
	if ((dwCreationFlags & DSBCAPS_CTRLFX) == 0)
	{
		for (i = 1; i<dwNumBuffers; i++)
		{
			if (FAILED(hr = m_pDS->DuplicateSoundBuffer(apDSBuffer[0], &apDSBuffer[i])))
			{
				DXTRACE_ERR(TEXT("DuplicateSoundBuffer"), hr);
				goto LFail;
			}
		}
	}
	else
	{
		for (i = 1; i<dwNumBuffers; i++)
		{
			hr = m_pDS->CreateSoundBuffer(&dsbd, &apDSBuffer[i], NULL);
			if (FAILED(hr))
			{
				DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);
				goto LFail;
			}
		}
	}

	// Create the sound
	*ppSound = new CSound(apDSBuffer, dwDSBufferSize, dwNumBuffers, pWaveFile, dwCreationFlags);

	SAFE_DELETE_ARRAY(apDSBuffer);
	return hrRet;

LFail:
	// Cleanup
	SAFE_DELETE(pWaveFile);
	SAFE_DELETE_ARRAY(apDSBuffer);
	return hr;
}









//-----------------------------------------------------------------------------
// Name: CSoundManager::CreateFromMemory()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT CSoundManager::CreateFromMemory(CSound** ppSound,
	BYTE* pbData,
	ULONG  ulDataSize,
	LPWAVEFORMATEX pwfx,
	DWORD dwCreationFlags,
	GUID guid3DAlgorithm,
	DWORD dwNumBuffers)
{
	HRESULT hr;
	DWORD   i;
	LPDIRECTSOUNDBUFFER* apDSBuffer = NULL;
	DWORD                dwDSBufferSize = NULL;
	CWaveFile*           pWaveFile = NULL;

	if (m_pDS == NULL)
		return CO_E_NOTINITIALIZED;
	if (pbData == NULL || ppSound == NULL || dwNumBuffers < 1)
		return E_INVALIDARG;

	apDSBuffer = new LPDIRECTSOUNDBUFFER[dwNumBuffers];
	if (apDSBuffer == NULL)
	{
		hr = E_OUTOFMEMORY;
		goto LFail;
	}

	pWaveFile = new CWaveFile();
	if (pWaveFile == NULL)
	{
		hr = E_OUTOFMEMORY;
		goto LFail;
	}

	pWaveFile->OpenFromMemory(pbData, ulDataSize, pwfx, WAVEFILE_READ);


	// Make the DirectSound buffer the same size as the wav file
	dwDSBufferSize = ulDataSize;

	// Create the direct sound buffer, and only request the flags needed
	// since each requires some overhead and limits if the buffer can 
	// be hardware accelerated
	DSBUFFERDESC dsbd;
	ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = dwCreationFlags;
	dsbd.dwBufferBytes = dwDSBufferSize;
	dsbd.guid3DAlgorithm = guid3DAlgorithm;
	dsbd.lpwfxFormat = pwfx;

	if (FAILED(hr = m_pDS->CreateSoundBuffer(&dsbd, &apDSBuffer[0], NULL)))
	{
		DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);
		goto LFail;
	}

	// Default to use DuplicateSoundBuffer() when created extra buffers since always 
	// create a buffer that uses the same memory however DuplicateSoundBuffer() will fail if 
	// DSBCAPS_CTRLFX is used, so use CreateSoundBuffer() instead in this case.
	if ((dwCreationFlags & DSBCAPS_CTRLFX) == 0)
	{
		for (i = 1; i<dwNumBuffers; i++)
		{
			if (FAILED(hr = m_pDS->DuplicateSoundBuffer(apDSBuffer[0], &apDSBuffer[i])))
			{
				DXTRACE_ERR(TEXT("DuplicateSoundBuffer"), hr);
				goto LFail;
			}
		}
	}
	else
	{
		for (i = 1; i<dwNumBuffers; i++)
		{
			hr = m_pDS->CreateSoundBuffer(&dsbd, &apDSBuffer[i], NULL);
			if (FAILED(hr))
			{
				DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);
				goto LFail;
			}
		}
	}

	// Create the sound
	*ppSound = new CSound(apDSBuffer, dwDSBufferSize, dwNumBuffers, pWaveFile, dwCreationFlags);

	SAFE_DELETE_ARRAY(apDSBuffer);
	return S_OK;

LFail:
	// Cleanup

	SAFE_DELETE_ARRAY(apDSBuffer);
	return hr;
}





//-----------------------------------------------------------------------------
// Name: CSoundManager::CreateStreaming()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT CSoundManager::CreateStreaming(CStreamingSound** ppStreamingSound,
	LPTSTR strWaveFileName,
	DWORD dwCreationFlags,
	GUID guid3DAlgorithm,
	DWORD dwNotifyCount,
	DWORD dwNotifySize,
	HANDLE hNotifyEvent)
{
	HRESULT hr;

	if (m_pDS == NULL)
		return CO_E_NOTINITIALIZED;
	if (strWaveFileName == NULL || ppStreamingSound == NULL || hNotifyEvent == NULL)
		return E_INVALIDARG;

	LPDIRECTSOUNDBUFFER pDSBuffer = NULL;
	DWORD               dwDSBufferSize = NULL;
	CWaveFile*          pWaveFile = NULL;
	DSBPOSITIONNOTIFY*  aPosNotify = NULL;
	LPDIRECTSOUNDNOTIFY pDSNotify = NULL;

	pWaveFile = new CWaveFile();
	if (pWaveFile == NULL)
		return E_OUTOFMEMORY;
	pWaveFile->Open(strWaveFileName, NULL, WAVEFILE_READ);

	// Figure out how big the DSound buffer should be 
	dwDSBufferSize = dwNotifySize * dwNotifyCount;

	// Set up the direct sound buffer.  Request the NOTIFY flag, so
	// that we are notified as the sound buffer plays.  Note, that using this flag
	// may limit the amount of hardware acceleration that can occur. 
	DSBUFFERDESC dsbd;
	ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = dwCreationFlags |
		DSBCAPS_CTRLPOSITIONNOTIFY |
		DSBCAPS_GETCURRENTPOSITION2;
	dsbd.dwBufferBytes = dwDSBufferSize;
	dsbd.guid3DAlgorithm = guid3DAlgorithm;
	dsbd.lpwfxFormat = pWaveFile->m_pwfx;

	if (FAILED(hr = m_pDS->CreateSoundBuffer(&dsbd, &pDSBuffer, NULL)))
	{
		// If wave format isn't then it will return 
		// either DSERR_BADFORMAT or E_INVALIDARG
		if (hr == DSERR_BADFORMAT || hr == E_INVALIDARG)
			return DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);

		return DXTRACE_ERR(TEXT("CreateSoundBuffer"), hr);
	}

	// Create the notification events, so that we know when to fill
	// the buffer as the sound plays. 
	if (FAILED(hr = pDSBuffer->QueryInterface(IID_IDirectSoundNotify,
		(VOID**)&pDSNotify)))
	{
		SAFE_DELETE_ARRAY(aPosNotify);
		return DXTRACE_ERR(TEXT("QueryInterface"), hr);
	}

	aPosNotify = new DSBPOSITIONNOTIFY[dwNotifyCount];
	if (aPosNotify == NULL)
		return E_OUTOFMEMORY;

	for (DWORD i = 0; i < dwNotifyCount; i++)
	{
		aPosNotify[i].dwOffset = (dwNotifySize * i) + dwNotifySize - 1;
		aPosNotify[i].hEventNotify = hNotifyEvent;
	}

	// Tell DirectSound when to notify us. The notification will come in the from 
	// of signaled events that are handled in WinMain()
	if (FAILED(hr = pDSNotify->SetNotificationPositions(dwNotifyCount,
		aPosNotify)))
	{
		SAFE_RELEASE(pDSNotify);
		SAFE_DELETE_ARRAY(aPosNotify);
		return DXTRACE_ERR(TEXT("SetNotificationPositions"), hr);
	}

	SAFE_RELEASE(pDSNotify);
	SAFE_DELETE_ARRAY(aPosNotify);

	// Create the sound
	*ppStreamingSound = new CStreamingSound(pDSBuffer, dwDSBufferSize, pWaveFile, dwNotifySize);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CSound::CSound()
// Desc: Constructs the class
//-----------------------------------------------------------------------------
CSound::CSound(LPDIRECTSOUNDBUFFER* apDSBuffer, DWORD dwDSBufferSize,
	DWORD dwNumBuffers, CWaveFile* pWaveFile, DWORD dwCreationFlags)
{
	DWORD i;

	m_apDSBuffer = new LPDIRECTSOUNDBUFFER[dwNumBuffers];
	if (NULL != m_apDSBuffer)
	{
		for (i = 0; i<dwNumBuffers; i++)
			m_apDSBuffer[i] = apDSBuffer[i];

		m_dwDSBufferSize = dwDSBufferSize;
		m_dwNumBuffers = dwNumBuffers;
		m_pWaveFile = pWaveFile;
		m_dwCreationFlags = dwCreationFlags;

		FillBufferWithSound(m_apDSBuffer[0], FALSE);
	}
}




//-----------------------------------------------------------------------------
// Name: CSound::~CSound()
// Desc: Destroys the class
//-----------------------------------------------------------------------------
CSound::~CSound()
{
	for (DWORD i = 0; i<m_dwNumBuffers; i++)
	{
		SAFE_RELEASE(m_apDSBuffer[i]);
	}

	SAFE_DELETE_ARRAY(m_apDSBuffer);
	SAFE_DELETE(m_pWaveFile);
}




//-----------------------------------------------------------------------------
// Name: CSound::FillBufferWithSound()
// Desc: Fills a DirectSound buffer with a sound file 
//-----------------------------------------------------------------------------
HRESULT CSound::FillBufferWithSound(LPDIRECTSOUNDBUFFER pDSB, BOOL bRepeatWavIfBufferLarger)
{
	HRESULT hr;
	VOID*   pDSLockedBuffer = NULL; // Pointer to locked buffer memory
	DWORD   dwDSLockedBufferSize = 0;    // Size of the locked DirectSound buffer
	DWORD   dwWavDataRead = 0;    // Amount of data read from the wav file 

	if (pDSB == NULL)
		return CO_E_NOTINITIALIZED;

	// Make sure we have focus, and we didn't just switch in from
	// an app which had a DirectSound device
	if (FAILED(hr = RestoreBuffer(pDSB, NULL)))
		return DXTRACE_ERR(TEXT("RestoreBuffer"), hr);

	// Lock the buffer down
	if (FAILED(hr = pDSB->Lock(0, m_dwDSBufferSize,
		&pDSLockedBuffer, &dwDSLockedBufferSize,
		NULL, NULL, 0L)))
		return DXTRACE_ERR(TEXT("Lock"), hr);

	// Reset the wave file to the beginning 
	m_pWaveFile->ResetFile();

	if (FAILED(hr = m_pWaveFile->Read((BYTE*)pDSLockedBuffer,
		dwDSLockedBufferSize,
		&dwWavDataRead)))
		return DXTRACE_ERR(TEXT("Read"), hr);

	if (dwWavDataRead == 0)
	{
		// Wav is blank, so just fill with silence
		FillMemory((BYTE*)pDSLockedBuffer,
			dwDSLockedBufferSize,
			(BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0));
	}
	else if (dwWavDataRead < dwDSLockedBufferSize)
	{
		// If the wav file was smaller than the DirectSound buffer, 
		// we need to fill the remainder of the buffer with data 
		if (bRepeatWavIfBufferLarger)
		{
			// Reset the file and fill the buffer with wav data
			DWORD dwReadSoFar = dwWavDataRead;    // From previous call above.
			while (dwReadSoFar < dwDSLockedBufferSize)
			{
				// This will keep reading in until the buffer is full 
				// for very short files
				if (FAILED(hr = m_pWaveFile->ResetFile()))
					return DXTRACE_ERR(TEXT("ResetFile"), hr);

				hr = m_pWaveFile->Read((BYTE*)pDSLockedBuffer + dwReadSoFar,
					dwDSLockedBufferSize - dwReadSoFar,
					&dwWavDataRead);
				if (FAILED(hr))
					return DXTRACE_ERR(TEXT("Read"), hr);

				dwReadSoFar += dwWavDataRead;
			}
		}
		else
		{
			// Don't repeat the wav file, just fill in silence 
			FillMemory((BYTE*)pDSLockedBuffer + dwWavDataRead,
				dwDSLockedBufferSize - dwWavDataRead,
				(BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0));
		}
	}

	// Unlock the buffer, we don't need it anymore.
	pDSB->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CSound::RestoreBuffer()
// Desc: Restores the lost buffer. *pbWasRestored returns TRUE if the buffer was 
//       restored.  It can also NULL if the information is not needed.
//-----------------------------------------------------------------------------
HRESULT CSound::RestoreBuffer(LPDIRECTSOUNDBUFFER pDSB, BOOL* pbWasRestored)
{
	HRESULT hr;

	if (pDSB == NULL)
		return CO_E_NOTINITIALIZED;
	if (pbWasRestored)
		*pbWasRestored = FALSE;

	DWORD dwStatus;
	if (FAILED(hr = pDSB->GetStatus(&dwStatus)))
		return DXTRACE_ERR(TEXT("GetStatus"), hr);

	if (dwStatus & DSBSTATUS_BUFFERLOST)
	{
		// Since the app could have just been activated, then
		// DirectSound may not be giving us control yet, so 
		// the restoring the buffer may fail.  
		// If it does, sleep until DirectSound gives us control.
		do
		{
			hr = pDSB->Restore();
			if (hr == DSERR_BUFFERLOST)
				Sleep(10);
		} while ((hr = pDSB->Restore()) == DSERR_BUFFERLOST);

		if (pbWasRestored != NULL)
			*pbWasRestored = TRUE;

		return S_OK;
	}
	else
	{
		return S_FALSE;
	}
}




//-----------------------------------------------------------------------------
// Name: CSound::GetFreeBuffer()
// Desc: Finding the first buffer that is not playing and return a pointer to 
//       it, or if all are playing return a pointer to a randomly selected buffer.
//-----------------------------------------------------------------------------
LPDIRECTSOUNDBUFFER CSound::GetFreeBuffer()
{
	if (m_apDSBuffer == NULL)
		return FALSE;

	for (DWORD i = 0; i<m_dwNumBuffers; i++)
	{
		if (m_apDSBuffer[i])
		{
			DWORD dwStatus = 0;
			m_apDSBuffer[i]->GetStatus(&dwStatus);
			if ((dwStatus & DSBSTATUS_PLAYING) == 0)
				break;
		}
	}
	int i = 0;
	if (i != m_dwNumBuffers)
		return m_apDSBuffer[i];
	else
		return m_apDSBuffer[rand() % m_dwNumBuffers];
}




//-----------------------------------------------------------------------------
// Name: CSound::GetBuffer()
// Desc: 
//-----------------------------------------------------------------------------
LPDIRECTSOUNDBUFFER CSound::GetBuffer(DWORD dwIndex)
{
	if (m_apDSBuffer == NULL)
		return NULL;
	if (dwIndex >= m_dwNumBuffers)
		return NULL;

	return m_apDSBuffer[dwIndex];
}




//-----------------------------------------------------------------------------
// Name: CSound::Get3DBufferInterface()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT CSound::Get3DBufferInterface(DWORD dwIndex, LPDIRECTSOUND3DBUFFER* ppDS3DBuffer)
{
	if (m_apDSBuffer == NULL)
		return CO_E_NOTINITIALIZED;
	if (dwIndex >= m_dwNumBuffers)
		return E_INVALIDARG;

	*ppDS3DBuffer = NULL;

	return m_apDSBuffer[dwIndex]->QueryInterface(IID_IDirectSound3DBuffer,
		(VOID**)ppDS3DBuffer);
}


//-----------------------------------------------------------------------------
// Name: CSound::Play()
// Desc: Plays the sound using voice management flags.  Pass in DSBPLAY_LOOPING
//       in the dwFlags to loop the sound
//-----------------------------------------------------------------------------
HRESULT CSound::Play(DWORD dwPriority, DWORD dwFlags, LONG lVolume, LONG lFrequency, LONG lPan)
{
	HRESULT hr;
	BOOL    bRestored;

	if (m_apDSBuffer == NULL)
		return CO_E_NOTINITIALIZED;

	LPDIRECTSOUNDBUFFER pDSB = GetFreeBuffer();

	if (pDSB == NULL)
		return DXTRACE_ERR(TEXT("GetFreeBuffer"), E_FAIL);

	// Restore the buffer if it was lost
	if (FAILED(hr = RestoreBuffer(pDSB, &bRestored)))
		return DXTRACE_ERR(TEXT("RestoreBuffer"), hr);

	if (bRestored)
	{
		// The buffer was restored, so we need to fill it with new data
		if (FAILED(hr = FillBufferWithSound(pDSB, FALSE)))
			return DXTRACE_ERR(TEXT("FillBufferWithSound"), hr);
	}

	if (m_dwCreationFlags & DSBCAPS_CTRLVOLUME)
	{
		pDSB->SetVolume(lVolume);
	}

	if (lFrequency != -1 &&
		(m_dwCreationFlags & DSBCAPS_CTRLFREQUENCY))
	{
		pDSB->SetFrequency(lFrequency);
	}

	if (m_dwCreationFlags & DSBCAPS_CTRLPAN)
	{
		pDSB->SetPan(lPan);
	}

	return pDSB->Play(0, dwPriority, dwFlags);
}




//-----------------------------------------------------------------------------
// Name: CSound::Play3D()
// Desc: Plays the sound using voice management flags.  Pass in DSBPLAY_LOOPING
//       in the dwFlags to loop the sound
//-----------------------------------------------------------------------------
HRESULT CSound::Play3D(LPDS3DBUFFER p3DBuffer, DWORD dwPriority, DWORD dwFlags, LONG lFrequency)
{
	HRESULT hr;
	BOOL    bRestored;
	DWORD   dwBaseFrequency;

	if (m_apDSBuffer == NULL)
		return CO_E_NOTINITIALIZED;

	LPDIRECTSOUNDBUFFER pDSB = GetFreeBuffer();
	if (pDSB == NULL)
		return DXTRACE_ERR(TEXT("GetFreeBuffer"), E_FAIL);

	// Restore the buffer if it was lost
	if (FAILED(hr = RestoreBuffer(pDSB, &bRestored)))
		return DXTRACE_ERR(TEXT("RestoreBuffer"), hr);

	if (bRestored)
	{
		// The buffer was restored, so we need to fill it with new data
		if (FAILED(hr = FillBufferWithSound(pDSB, FALSE)))
			return DXTRACE_ERR(TEXT("FillBufferWithSound"), hr);
	}

	if (m_dwCreationFlags & DSBCAPS_CTRLFREQUENCY)
	{
		pDSB->GetFrequency(&dwBaseFrequency);
		pDSB->SetFrequency(dwBaseFrequency + lFrequency);
	}

	// QI for the 3D buffer 
	LPDIRECTSOUND3DBUFFER pDS3DBuffer;
	hr = pDSB->QueryInterface(IID_IDirectSound3DBuffer, (VOID**)&pDS3DBuffer);
	if (SUCCEEDED(hr))
	{
		hr = pDS3DBuffer->SetAllParameters(p3DBuffer, DS3D_IMMEDIATE);
		if (SUCCEEDED(hr))
		{
			hr = pDSB->Play(0, dwPriority, dwFlags);
		}

		pDS3DBuffer->Release();
	}

	return hr;
}



//-----------------------------------------------------------------------------
// Name: CSound::Stop()
// Desc: Stops the sound from playing
//-----------------------------------------------------------------------------
HRESULT CSound::Stop()
{
	if (m_apDSBuffer == NULL)
		return CO_E_NOTINITIALIZED;

	HRESULT hr = 0;

	for (DWORD i = 0; i<m_dwNumBuffers; i++)
		hr |= m_apDSBuffer[i]->Stop();

	return hr;
}




//-----------------------------------------------------------------------------
// Name: CSound::Reset()
// Desc: Reset all of the sound buffers
//-----------------------------------------------------------------------------
HRESULT CSound::Reset()
{
	if (m_apDSBuffer == NULL)
		return CO_E_NOTINITIALIZED;

	HRESULT hr = 0;

	for (DWORD i = 0; i<m_dwNumBuffers; i++)
		hr |= m_apDSBuffer[i]->SetCurrentPosition(0);

	return hr;
}




//-----------------------------------------------------------------------------
// Name: CSound::IsSoundPlaying()
// Desc: Checks to see if a buffer is playing and returns TRUE if it is.
//-----------------------------------------------------------------------------
BOOL CSound::IsSoundPlaying()
{
	BOOL bIsPlaying = FALSE;

	if (m_apDSBuffer == NULL)
		return FALSE;

	for (DWORD i = 0; i<m_dwNumBuffers; i++)
	{
		if (m_apDSBuffer[i])
		{
			DWORD dwStatus = 0;
			m_apDSBuffer[i]->GetStatus(&dwStatus);
			bIsPlaying |= ((dwStatus & DSBSTATUS_PLAYING) != 0);
		}
	}

	return bIsPlaying;
}




//-----------------------------------------------------------------------------
// Name: CStreamingSound::CStreamingSound()
// Desc: Setups up a buffer so data can be streamed from the wave file into 
//       a buffer.  This is very useful for large wav files that would take a 
//       while to load.  The buffer is initially filled with data, then 
//       as sound is played the notification events are signaled and more data
//       is written into the buffer by calling HandleWaveStreamNotification()
//-----------------------------------------------------------------------------
CStreamingSound::CStreamingSound(LPDIRECTSOUNDBUFFER pDSBuffer, DWORD dwDSBufferSize,
	CWaveFile* pWaveFile, DWORD dwNotifySize)
	: CSound(&pDSBuffer, dwDSBufferSize, 1, pWaveFile, 0)
{
	m_dwLastPlayPos = 0;
	m_dwPlayProgress = 0;
	m_dwNotifySize = dwNotifySize;
	m_dwNextWriteOffset = 0;
	m_bFillNextNotificationWithSilence = FALSE;
}




//-----------------------------------------------------------------------------
// Name: CStreamingSound::~CStreamingSound()
// Desc: Destroys the class
//-----------------------------------------------------------------------------
CStreamingSound::~CStreamingSound()
{
}



//-----------------------------------------------------------------------------
// Name: CStreamingSound::HandleWaveStreamNotification()
// Desc: Handle the notification that tells us to put more wav data in the 
//       circular buffer
//-----------------------------------------------------------------------------
HRESULT CStreamingSound::HandleWaveStreamNotification(BOOL bLoopedPlay)
{
	HRESULT hr;
	DWORD   dwCurrentPlayPos;
	DWORD   dwPlayDelta;
	DWORD   dwBytesWrittenToBuffer;
	VOID*   pDSLockedBuffer = NULL;
	VOID*   pDSLockedBuffer2 = NULL;
	DWORD   dwDSLockedBufferSize;
	DWORD   dwDSLockedBufferSize2;

	if (m_apDSBuffer == NULL || m_pWaveFile == NULL)
		return CO_E_NOTINITIALIZED;

	// Restore the buffer if it was lost
	BOOL bRestored;
	if (FAILED(hr = RestoreBuffer(m_apDSBuffer[0], &bRestored)))
		return DXTRACE_ERR(TEXT("RestoreBuffer"), hr);

	if (bRestored)
	{
		// The buffer was restored, so we need to fill it with new data
		if (FAILED(hr = FillBufferWithSound(m_apDSBuffer[0], FALSE)))
			return DXTRACE_ERR(TEXT("FillBufferWithSound"), hr);
		return S_OK;
	}

	// Lock the DirectSound buffer
	if (FAILED(hr = m_apDSBuffer[0]->Lock(m_dwNextWriteOffset, m_dwNotifySize,
		&pDSLockedBuffer, &dwDSLockedBufferSize,
		&pDSLockedBuffer2, &dwDSLockedBufferSize2, 0L)))
		return DXTRACE_ERR(TEXT("Lock"), hr);

	// m_dwDSBufferSize and m_dwNextWriteOffset are both multiples of m_dwNotifySize, 
	// it should the second buffer, so it should never be valid
	if (pDSLockedBuffer2 != NULL)
		return E_UNEXPECTED;

	if (!m_bFillNextNotificationWithSilence)
	{
		// Fill the DirectSound buffer with wav data
		if (FAILED(hr = m_pWaveFile->Read((BYTE*)pDSLockedBuffer,
			dwDSLockedBufferSize,
			&dwBytesWrittenToBuffer)))
			return DXTRACE_ERR(TEXT("Read"), hr);
	}
	else
	{
		// Fill the DirectSound buffer with silence
		FillMemory(pDSLockedBuffer, dwDSLockedBufferSize,
			(BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0));
		dwBytesWrittenToBuffer = dwDSLockedBufferSize;
	}

	// If the number of bytes written is less than the 
	// amount we requested, we have a short file.
	if (dwBytesWrittenToBuffer < dwDSLockedBufferSize)
	{
		if (!bLoopedPlay)
		{
			// Fill in silence for the rest of the buffer.
			FillMemory((BYTE*)pDSLockedBuffer + dwBytesWrittenToBuffer,
				dwDSLockedBufferSize - dwBytesWrittenToBuffer,
				(BYTE)(m_pWaveFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0));

			// Any future notifications should just fill the buffer with silence
			m_bFillNextNotificationWithSilence = TRUE;
		}
		else
		{
			// We are looping, so reset the file and fill the buffer with wav data
			DWORD dwReadSoFar = dwBytesWrittenToBuffer;    // From previous call above.
			while (dwReadSoFar < dwDSLockedBufferSize)
			{
				// This will keep reading in until the buffer is full (for very short files).
				if (FAILED(hr = m_pWaveFile->ResetFile()))
					return DXTRACE_ERR(TEXT("ResetFile"), hr);

				if (FAILED(hr = m_pWaveFile->Read((BYTE*)pDSLockedBuffer + dwReadSoFar,
					dwDSLockedBufferSize - dwReadSoFar,
					&dwBytesWrittenToBuffer)))
					return DXTRACE_ERR(TEXT("Read"), hr);

				dwReadSoFar += dwBytesWrittenToBuffer;
			}
		}
	}

	// Unlock the DirectSound buffer
	m_apDSBuffer[0]->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0);

	// Figure out how much data has been played so far.  When we have played
	// past the end of the file, we will either need to start filling the
	// buffer with silence or starting reading from the beginning of the file, 
	// depending if the user wants to loop the sound
	if (FAILED(hr = m_apDSBuffer[0]->GetCurrentPosition(&dwCurrentPlayPos, NULL)))
		return DXTRACE_ERR(TEXT("GetCurrentPosition"), hr);

	// Check to see if the position counter looped
	if (dwCurrentPlayPos < m_dwLastPlayPos)
		dwPlayDelta = (m_dwDSBufferSize - m_dwLastPlayPos) + dwCurrentPlayPos;
	else
		dwPlayDelta = dwCurrentPlayPos - m_dwLastPlayPos;

	m_dwPlayProgress += dwPlayDelta;
	m_dwLastPlayPos = dwCurrentPlayPos;

	// If we are now filling the buffer with silence, then we have found the end so 
	// check to see if the entire sound has played, if it has then stop the buffer.
	if (m_bFillNextNotificationWithSilence)
	{
		// We don't want to cut off the sound before it's done playing.
		if (m_dwPlayProgress >= m_pWaveFile->GetSize())
		{
			m_apDSBuffer[0]->Stop();
		}
	}

	// Update where the buffer will lock (for next time)
	m_dwNextWriteOffset += dwDSLockedBufferSize;
	m_dwNextWriteOffset %= m_dwDSBufferSize; // Circular buffer

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CStreamingSound::Reset()
// Desc: Resets the sound so it will begin playing at the beginning
//-----------------------------------------------------------------------------
HRESULT CStreamingSound::Reset()
{
	HRESULT hr;

	if (m_apDSBuffer[0] == NULL || m_pWaveFile == NULL)
		return CO_E_NOTINITIALIZED;

	m_dwLastPlayPos = 0;
	m_dwPlayProgress = 0;
	m_dwNextWriteOffset = 0;
	m_bFillNextNotificationWithSilence = FALSE;

	// Restore the buffer if it was lost
	BOOL bRestored;
	if (FAILED(hr = RestoreBuffer(m_apDSBuffer[0], &bRestored)))
		return DXTRACE_ERR(TEXT("RestoreBuffer"), hr);

	if (bRestored)
	{
		// The buffer was restored, so we need to fill it with new data
		if (FAILED(hr = FillBufferWithSound(m_apDSBuffer[0], FALSE)))
			return DXTRACE_ERR(TEXT("FillBufferWithSound"), hr);
	}

	m_pWaveFile->ResetFile();

	return m_apDSBuffer[0]->SetCurrentPosition(0L);
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::CWaveFile()
// Desc: Constructs the class.  Call Open() to open a wave file for reading.  
//       Then call Read() as needed.  Calling the destructor or Close() 
//       will close the file.  
//-----------------------------------------------------------------------------
CWaveFile::CWaveFile()
{
	m_pwfx = NULL;
	m_hmmio = NULL;
	m_pResourceBuffer = NULL;
	m_dwSize = 0;
	m_bIsReadingFromMemory = FALSE;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::~CWaveFile()
// Desc: Destructs the class
//-----------------------------------------------------------------------------
CWaveFile::~CWaveFile()
{
	Close();

	if (!m_bIsReadingFromMemory)
		SAFE_DELETE_ARRAY(m_pwfx);
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Open()
// Desc: Opens a wave file for reading
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Open(LPTSTR strFileName, WAVEFORMATEX* pwfx, DWORD dwFlags)
{
	HRESULT hr;

	m_dwFlags = dwFlags;
	m_bIsReadingFromMemory = FALSE;

	if (m_dwFlags == WAVEFILE_READ)
	{
		if (strFileName == NULL)
			return E_INVALIDARG;
		SAFE_DELETE_ARRAY(m_pwfx);

		m_hmmio = mmioOpen(strFileName, NULL, MMIO_ALLOCBUF | MMIO_READ);

		if (NULL == m_hmmio)
		{
			HRSRC   hResInfo;
			HGLOBAL hResData;
			DWORD   dwSize;
			VOID*   pvRes;

			// Loading it as a file failed, so try it as a resource
			if (NULL == (hResInfo = FindResource(NULL, strFileName, TEXT("WAVE"))))
			{
				if (NULL == (hResInfo = FindResource(NULL, strFileName, TEXT("WAV"))))
					return DXTRACE_ERR(TEXT("FindResource"), E_FAIL);
			}

			if (NULL == (hResData = LoadResource(NULL, hResInfo)))
				return DXTRACE_ERR(TEXT("LoadResource"), E_FAIL);

			if (0 == (dwSize = SizeofResource(NULL, hResInfo)))
				return DXTRACE_ERR(TEXT("SizeofResource"), E_FAIL);

			if (NULL == (pvRes = LockResource(hResData)))
				return DXTRACE_ERR(TEXT("LockResource"), E_FAIL);

			m_pResourceBuffer = new CHAR[dwSize];
			memcpy(m_pResourceBuffer, pvRes, dwSize);

			MMIOINFO mmioInfo;
			ZeroMemory(&mmioInfo, sizeof(mmioInfo));
			mmioInfo.fccIOProc = FOURCC_MEM;
			mmioInfo.cchBuffer = dwSize;
			mmioInfo.pchBuffer = (CHAR*)m_pResourceBuffer;

			m_hmmio = mmioOpen(NULL, &mmioInfo, MMIO_ALLOCBUF | MMIO_READ);
		}

		if (FAILED(hr = ReadMMIO()))
		{
			// ReadMMIO will fail if its an not a wave file
			mmioClose(m_hmmio, 0);
			return DXTRACE_ERR(TEXT("ReadMMIO"), hr);
		}

		if (FAILED(hr = ResetFile()))
			return DXTRACE_ERR(TEXT("ResetFile"), hr);

		// After the reset, the size of the wav file is m_ck.cksize so store it now
		m_dwSize = m_ck.cksize;
	}
	else
	{
		m_hmmio = mmioOpen(strFileName, NULL, MMIO_ALLOCBUF |
			MMIO_READWRITE |
			MMIO_CREATE);
		if (NULL == m_hmmio)
			return DXTRACE_ERR(TEXT("mmioOpen"), E_FAIL);

		if (FAILED(hr = WriteMMIO(pwfx)))
		{
			mmioClose(m_hmmio, 0);
			return DXTRACE_ERR(TEXT("WriteMMIO"), hr);
		}

		if (FAILED(hr = ResetFile()))
			return DXTRACE_ERR(TEXT("ResetFile"), hr);
	}

	return hr;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::OpenFromMemory()
// Desc: copy data to CWaveFile member variable from memory
//-----------------------------------------------------------------------------
HRESULT CWaveFile::OpenFromMemory(BYTE* pbData, ULONG ulDataSize,
	WAVEFORMATEX* pwfx, DWORD dwFlags)
{
	m_pwfx = pwfx;
	m_ulDataSize = ulDataSize;
	m_pbData = pbData;
	m_pbDataCur = m_pbData;
	m_bIsReadingFromMemory = TRUE;

	if (dwFlags != WAVEFILE_READ)
		return E_NOTIMPL;

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::ReadMMIO()
// Desc: Support function for reading from a multimedia I/O stream.
//       m_hmmio must be valid before calling.  This function uses it to
//       update m_ckRiff, and m_pwfx. 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::ReadMMIO()
{
	MMCKINFO        ckIn;           // chunk info. for general use.
	PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.       

	m_pwfx = NULL;

	if ((0 != mmioDescend(m_hmmio, &m_ckRiff, NULL, 0)))
		return DXTRACE_ERR(TEXT("mmioDescend"), E_FAIL);

	// Check to make sure this is a valid wave file
	if ((m_ckRiff.ckid != FOURCC_RIFF) ||
		(m_ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
		return DXTRACE_ERR(TEXT("mmioFOURCC"), E_FAIL);

	// Search the input file for for the 'fmt ' chunk.
	ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
	if (0 != mmioDescend(m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK))
		return DXTRACE_ERR(TEXT("mmioDescend"), E_FAIL);

	// Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
	// if there are extra parameters at the end, we'll ignore them
	if (ckIn.cksize < (LONG) sizeof(PCMWAVEFORMAT))
		return DXTRACE_ERR(TEXT("sizeof(PCMWAVEFORMAT)"), E_FAIL);

	// Read the 'fmt ' chunk into <pcmWaveFormat>.
	if (mmioRead(m_hmmio, (HPSTR)&pcmWaveFormat,
		sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat))
		return DXTRACE_ERR(TEXT("mmioRead"), E_FAIL);

	// Allocate the waveformatex, but if its not pcm format, read the next
	// word, and thats how many extra bytes to allocate.
	if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM)
	{
		m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)];
		if (NULL == m_pwfx)
			return DXTRACE_ERR(TEXT("m_pwfx"), E_FAIL);

		// Copy the bytes from the pcm structure to the waveformatex structure
		memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
		m_pwfx->cbSize = 0;
	}
	else
	{
		// Read in length of extra bytes.
		WORD cbExtraBytes = 0L;
		if (mmioRead(m_hmmio, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD))
			return DXTRACE_ERR(TEXT("mmioRead"), E_FAIL);

		m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)+cbExtraBytes];
		if (NULL == m_pwfx)
			return DXTRACE_ERR(TEXT("new"), E_FAIL);

		// Copy the bytes from the pcm structure to the waveformatex structure
		memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
		m_pwfx->cbSize = cbExtraBytes;

		// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
		if (mmioRead(m_hmmio, (CHAR*)(((BYTE*)&(m_pwfx->cbSize)) + sizeof(WORD)),
			cbExtraBytes) != cbExtraBytes)
		{
			SAFE_DELETE(m_pwfx);
			return DXTRACE_ERR(TEXT("mmioRead"), E_FAIL);
		}
	}

	// Ascend the input file out of the 'fmt ' chunk.
	if (0 != mmioAscend(m_hmmio, &ckIn, 0))
	{
		SAFE_DELETE(m_pwfx);
		return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);
	}

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::GetSize()
// Desc: Retuns the size of the read access wave file 
//-----------------------------------------------------------------------------
DWORD CWaveFile::GetSize()
{
	return m_dwSize;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::ResetFile()
// Desc: Resets the internal m_ck pointer so reading starts from the 
//       beginning of the file again 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::ResetFile()
{
	if (m_bIsReadingFromMemory)
	{
		m_pbDataCur = m_pbData;
	}
	else
	{
		if (m_hmmio == NULL)
			return CO_E_NOTINITIALIZED;

		if (m_dwFlags == WAVEFILE_READ)
		{
			// Seek to the data
			if (-1 == mmioSeek(m_hmmio, m_ckRiff.dwDataOffset + sizeof(FOURCC),
				SEEK_SET))
				return DXTRACE_ERR(TEXT("mmioSeek"), E_FAIL);

			// Search the input file for the 'data' chunk.
			m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
			if (0 != mmioDescend(m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK))
				return DXTRACE_ERR(TEXT("mmioDescend"), E_FAIL);
		}
		else
		{
			// Create the 'data' chunk that holds the waveform samples.  
			m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
			m_ck.cksize = 0;

			if (0 != mmioCreateChunk(m_hmmio, &m_ck, 0))
				return DXTRACE_ERR(TEXT("mmioCreateChunk"), E_FAIL);

			if (0 != mmioGetInfo(m_hmmio, &m_mmioinfoOut, 0))
				return DXTRACE_ERR(TEXT("mmioGetInfo"), E_FAIL);
		}
	}

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Read()
// Desc: Reads section of data from a wave file into pBuffer and returns 
//       how much read in pdwSizeRead, reading not more than dwSizeToRead.
//       This uses m_ck to determine where to start reading from.  So 
//       subsequent calls will be continue where the last left off unless 
//       Reset() is called.
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Read(BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead)
{
	if (m_bIsReadingFromMemory)
	{
		if (m_pbDataCur == NULL)
			return CO_E_NOTINITIALIZED;
		if (pdwSizeRead != NULL)
			*pdwSizeRead = 0;

		if ((BYTE*)(m_pbDataCur + dwSizeToRead) >
			(BYTE*)(m_pbData + m_ulDataSize))
		{
			dwSizeToRead = m_ulDataSize - (DWORD)(m_pbDataCur - m_pbData);
		}

		CopyMemory(pBuffer, m_pbDataCur, dwSizeToRead);

		if (pdwSizeRead != NULL)
			*pdwSizeRead = dwSizeToRead;

		return S_OK;
	}
	else
	{
		MMIOINFO mmioinfoIn; // current status of m_hmmio

		if (m_hmmio == NULL)
			return CO_E_NOTINITIALIZED;
		if (pBuffer == NULL || pdwSizeRead == NULL)
			return E_INVALIDARG;

		if (pdwSizeRead != NULL)
			*pdwSizeRead = 0;

		if (0 != mmioGetInfo(m_hmmio, &mmioinfoIn, 0))
			return DXTRACE_ERR(TEXT("mmioGetInfo"), E_FAIL);

		UINT cbDataIn = dwSizeToRead;
		if (cbDataIn > m_ck.cksize)
			cbDataIn = m_ck.cksize;

		m_ck.cksize -= cbDataIn;

		for (DWORD cT = 0; cT < cbDataIn; cT++)
		{
			// Copy the bytes from the io to the buffer.
			if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
			{
				if (0 != mmioAdvance(m_hmmio, &mmioinfoIn, MMIO_READ))
					return DXTRACE_ERR(TEXT("mmioAdvance"), E_FAIL);

				if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
					return DXTRACE_ERR(TEXT("mmioinfoIn.pchNext"), E_FAIL);
			}

			// Actual copy.
			*((BYTE*)pBuffer + cT) = *((BYTE*)mmioinfoIn.pchNext);
			mmioinfoIn.pchNext++;
		}

		if (0 != mmioSetInfo(m_hmmio, &mmioinfoIn, 0))
			return DXTRACE_ERR(TEXT("mmioSetInfo"), E_FAIL);

		if (pdwSizeRead != NULL)
			*pdwSizeRead = cbDataIn;

		return S_OK;
	}
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Close()
// Desc: Closes the wave file 
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Close()
{
	if (m_dwFlags == WAVEFILE_READ)
	{
		mmioClose(m_hmmio, 0);
		m_hmmio = NULL;
		SAFE_DELETE_ARRAY(m_pResourceBuffer);
	}
	else
	{
		m_mmioinfoOut.dwFlags |= MMIO_DIRTY;

		if (m_hmmio == NULL)
			return CO_E_NOTINITIALIZED;

		if (0 != mmioSetInfo(m_hmmio, &m_mmioinfoOut, 0))
			return DXTRACE_ERR(TEXT("mmioSetInfo"), E_FAIL);

		// Ascend the output file out of the 'data' chunk -- this will cause
		// the chunk size of the 'data' chunk to be written.
		if (0 != mmioAscend(m_hmmio, &m_ck, 0))
			return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);

		// Do this here instead...
		if (0 != mmioAscend(m_hmmio, &m_ckRiff, 0))
			return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);

		mmioSeek(m_hmmio, 0, SEEK_SET);

		if (0 != (INT)mmioDescend(m_hmmio, &m_ckRiff, NULL, 0))
			return DXTRACE_ERR(TEXT("mmioDescend"), E_FAIL);

		m_ck.ckid = mmioFOURCC('f', 'a', 'c', 't');

		if (0 == mmioDescend(m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK))
		{
			DWORD dwSamples = 0;
			mmioWrite(m_hmmio, (HPSTR)&dwSamples, sizeof(DWORD));
			mmioAscend(m_hmmio, &m_ck, 0);
		}

		// Ascend the output file out of the 'RIFF' chunk -- this will cause
		// the chunk size of the 'RIFF' chunk to be written.
		if (0 != mmioAscend(m_hmmio, &m_ckRiff, 0))
			return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);

		mmioClose(m_hmmio, 0);
		m_hmmio = NULL;
	}

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::WriteMMIO()
// Desc: Support function for reading from a multimedia I/O stream
//       pwfxDest is the WAVEFORMATEX for this new wave file.  
//       m_hmmio must be valid before calling.  This function uses it to
//       update m_ckRiff, and m_ck.  
//-----------------------------------------------------------------------------
HRESULT CWaveFile::WriteMMIO(WAVEFORMATEX *pwfxDest)
{
	DWORD    dwFactChunk; // Contains the actual fact chunk. Garbage until WaveCloseWriteFile.
	MMCKINFO ckOut1;

	dwFactChunk = (DWORD)-1;

	// Create the output file RIFF chunk of form type 'WAVE'.
	m_ckRiff.fccType = mmioFOURCC('W', 'A', 'V', 'E');
	m_ckRiff.cksize = 0;

	if (0 != mmioCreateChunk(m_hmmio, &m_ckRiff, MMIO_CREATERIFF))
		return DXTRACE_ERR(TEXT("mmioCreateChunk"), E_FAIL);

	// We are now descended into the 'RIFF' chunk we just created.
	// Now create the 'fmt ' chunk. Since we know the size of this chunk,
	// specify it in the MMCKINFO structure so MMIO doesn't have to seek
	// back and set the chunk size after ascending from the chunk.
	m_ck.ckid = mmioFOURCC('f', 'm', 't', ' ');
	m_ck.cksize = sizeof(PCMWAVEFORMAT);

	if (0 != mmioCreateChunk(m_hmmio, &m_ck, 0))
		return DXTRACE_ERR(TEXT("mmioCreateChunk"), E_FAIL);

	// Write the PCMWAVEFORMAT structure to the 'fmt ' chunk if its that type. 
	if (pwfxDest->wFormatTag == WAVE_FORMAT_PCM)
	{
		if (mmioWrite(m_hmmio, (HPSTR)pwfxDest,
			sizeof(PCMWAVEFORMAT)) != sizeof(PCMWAVEFORMAT))
			return DXTRACE_ERR(TEXT("mmioWrite"), E_FAIL);
	}
	else
	{
		// Write the variable length size.
		if ((UINT)mmioWrite(m_hmmio, (HPSTR)pwfxDest,
			sizeof(*pwfxDest) + pwfxDest->cbSize) !=
			(sizeof(*pwfxDest) + pwfxDest->cbSize))
			return DXTRACE_ERR(TEXT("mmioWrite"), E_FAIL);
	}

	// Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
	if (0 != mmioAscend(m_hmmio, &m_ck, 0))
		return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);

	// Now create the fact chunk, not required for PCM but nice to have.  This is filled
	// in when the close routine is called.
	ckOut1.ckid = mmioFOURCC('f', 'a', 'c', 't');
	ckOut1.cksize = 0;

	if (0 != mmioCreateChunk(m_hmmio, &ckOut1, 0))
		return DXTRACE_ERR(TEXT("mmioCreateChunk"), E_FAIL);

	if (mmioWrite(m_hmmio, (HPSTR)&dwFactChunk, sizeof(dwFactChunk)) !=
		sizeof(dwFactChunk))
		return DXTRACE_ERR(TEXT("mmioWrite"), E_FAIL);

	// Now ascend out of the fact chunk...
	if (0 != mmioAscend(m_hmmio, &ckOut1, 0))
		return DXTRACE_ERR(TEXT("mmioAscend"), E_FAIL);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CWaveFile::Write()
// Desc: Writes data to the open wave file
//-----------------------------------------------------------------------------
HRESULT CWaveFile::Write(UINT nSizeToWrite, BYTE* pbSrcData, UINT* pnSizeWrote)
{
	UINT cT;

	if (m_bIsReadingFromMemory)
		return E_NOTIMPL;
	if (m_hmmio == NULL)
		return CO_E_NOTINITIALIZED;
	if (pnSizeWrote == NULL || pbSrcData == NULL)
		return E_INVALIDARG;

	*pnSizeWrote = 0;

	for (cT = 0; cT < nSizeToWrite; cT++)
	{
		if (m_mmioinfoOut.pchNext == m_mmioinfoOut.pchEndWrite)
		{
			m_mmioinfoOut.dwFlags |= MMIO_DIRTY;
			if (0 != mmioAdvance(m_hmmio, &m_mmioinfoOut, MMIO_WRITE))
				return DXTRACE_ERR(TEXT("mmioAdvance"), E_FAIL);
		}

		*((BYTE*)m_mmioinfoOut.pchNext) = *((BYTE*)pbSrcData + cT);
		(BYTE*)m_mmioinfoOut.pchNext++;

		(*pnSizeWrote)++;
	}

	return S_OK;
}




